package contegor.scire.core.queries;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import contegor.scire.core.Concept;
import contegor.scire.core.Container;
import contegor.scire.core.Id;
import contegor.scire.core.IdFragment;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.NeedsPrecedent;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointAdapter;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.Version;
import contegor.scire.core.WithConsequences;
import contegor.scire.core.WithSubsequentsExtensible;

/**
 * Given {@link Query}s that return {@link Concept}s, this one returns the {@link Link}s who's targets are 
 * those {@link Concept}s.
 *
 * @version $Revision: 1.4 $
 * @author  Héctor Cirbián Pousa ($Author: hector $)
 */
@Version("$Id: GetInputLinks.java,v 1.4 2010-02-01 10:55:41 hector Exp $")
public class GetLinksFromTargets extends QueryWithDependencies<Concept, Link> implements WithConsequences<Link, Link>, NeedsPrecedent<Concept, Link>, WithSubsequentsExtensible<Link, Link> {
  private static final long serialVersionUID = 9128702584346234380L;

  private ConcurrentHashMap<Id, List<IdFragment>> idsMap = new ConcurrentHashMap<Id, List<IdFragment>>();
  private List<GetLinksFromTargetIds> consequences;
  private ReturnPoint<Concept> dependenciesReturnPoint = null;
  private ReturnPoint<Link> consequencesReturnPoint = null;
  private List<Link> consequencesResults = null;
  private List<NeedsPrecedent<? super Link, Link>> subsequents;

//  @SuppressWarnings("unchecked")
//  public GetLinksFromTargets(Id targetId) {
//    super(targetId);
//  }

//  @SuppressWarnings("unchecked")
//  public GetLinksFromTargets(Id targetId, Collection<? extends Concept> precedentResults) {
//    super(targetId);
//    this.setPrecedent(null, precedentResults);
//  }

  public GetLinksFromTargets(Query<Concept> dependencie) {
    super(dependencie);
  }

  public GetLinksFromTargets(Id targetId, Query<Concept> ... dependencie) {
    super(targetId, dependencie);
  }

//  /** <b>Internal use only.</b> */ // TODO: Try to avoid this.
//  public GetLinksFromTargets(TaskId taskId, Query<Concept> dependencie) {
//    super(taskId, dependencie);
//  }

  /** <b>Internal use only.</b> */ // TODO: Try to avoid this.
  @SuppressWarnings("unchecked")
  public GetLinksFromTargets(TaskId taskId, Id targetId, Collection<? extends Concept> precedentResults) {
    super(taskId, targetId);
    this.setPrecedent(null, precedentResults);
  }

  private void createDependenciesReturnPoint() {
    dependenciesReturnPoint = new ReturnPointAdapter<Concept>() {
      public void manage(TaskId taskId, Concept result) {
        includeResult(result);
      }
      public void manage(TaskId taskId, Exception exception) {
        returnPoint.manage(taskId, exception);
      }
    };
  }

  private void createConsequencesReturnPoint() {
    consequencesResults = Collections.synchronizedList(new ArrayList<Link>());
    consequencesReturnPoint = new ReturnPointAdapter<Link>() {
      public void manage(TaskId taskId, Link result) {
        consequencesResults.add(result);
      }
      public void manage(TaskId taskId, Exception exception) {
        returnPoint.manage(taskId, exception);
      }
    };
  }

  private void includeResult(Concept result) {
    Id               id  = result.getId();
    List<IdFragment> aux = idsMap.get(id.getParentId());
    if (aux == null) {
      aux = Collections.synchronizedList(new ArrayList<IdFragment>());
      List<IdFragment> previous = idsMap.putIfAbsent(id.getParentId(),aux);
      if (previous != null)
        aux = previous;
    }
    aux.add(id.getLastIdFragment());    
  }
  
  @Override
  public void setPrecedent(Task<? extends Concept> task, Collection<? extends Concept> results) {
    for (Concept c: results)
      includeResult(c);
  }

  public void process(Container container) {
    consequences = new ArrayList<GetLinksFromTargetIds>(idsMap.size());
    TaskId subTaskId = this.taskId;
    for (Map.Entry<Id, List<IdFragment>> entry: idsMap.entrySet()) {
      List<IdFragment> aux = entry.getValue();
      IdFragment[]     ids = new IdFragment[aux.size()];
      consequences.add(new GetLinksFromTargetIds(subTaskId = subTaskId.next(subTaskId == this.taskId), entry.getKey(), aux.toArray(ids)));
    }
  }

  @Override
  public List<? extends Task<Concept>> getDependencies() {
    return this.dependencies;
  }

  @Override
  public List<? extends ReturnPoint<Concept>> getDependenciesReturnPoints() {
    if ((this.dependencies == null) || (this.dependencies.size() == 0))
      return Collections.emptyList();
    if (this.dependenciesReturnPoint == null)
      createDependenciesReturnPoint();
    return new InfiniteSingletonList<ReturnPoint<Concept>>(this.dependenciesReturnPoint, this.dependencies.size());
  }

  @Override
  public List<? extends Task<Link>> getConsequences() {
    return this.consequences;
  }

  @Override
  public List<? extends ReturnPoint<? super Link>> getConsequencesReturnPoints() {
    if (this.consequences == null)
      return Collections.emptyList();
    if ((this.subsequents != null) && (this.subsequents.size() > 0)) {
      // In case we have subsequents, results should be only for them.
      createConsequencesReturnPoint();
      return new InfiniteSingletonList<ReturnPoint<? super Link>>(this.consequencesReturnPoint, this.consequences.size());
    }
    else
      return new InfiniteSingletonList<ReturnPoint<? super Link>>(this.returnPoint, this.consequences.size());
  }

  @Override
  public GetLinksFromTargets withIt(NeedsPrecedent<? super Link, Link> subsequent) {
    if (this.subsequents == null)
      this.subsequents = new LinkedList<NeedsPrecedent<? super Link,Link>>();
    this.subsequents.add(subsequent);
    return this;
  }

  @Override
  public List<? extends Collection<Link>> getResultsForSubsequentTasks() {
    if ((this.subsequents == null) || (this.subsequents.size() == 0))
      return Collections.emptyList();
    if (this.consequencesResults == null)
      this.consequencesResults = Collections.emptyList();
    return new InfiniteSingletonList<List<Link>>(this.consequencesResults, this.subsequents.size());
  }

  @Override
  public List<? extends NeedsPrecedent<? super Link, Link>> getSubsequentTasks() {
    if (this.subsequents == null)
      return Collections.emptyList();
    return this.subsequents;
  }

  @Override
  public List<? extends ReturnPoint<? super Link>> getSubsequentTasksReturnPoints() {
    if ((this.subsequents == null) || (this.subsequents.size() == 0))
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<? super Link>>(this.returnPoint, this.subsequents.size());
  }

  @Override
  public void setTaskId(TaskId taskId) {
    super.setTaskId(taskId);
    if (this.consequences != null)
      for (GetLinksFromTargetIds consequence: this.consequences)
        consequence.setTaskId(taskId.next(true));
    if (this.subsequents != null)
      for (NeedsPrecedent<?,?> subsequent: this.subsequents)
        subsequent.setTaskId(taskId.next(true));
  }
}